#include <QtGui>
#include <QGLWidget>
#include <GL/glut.h>

#include "Vector/vector.03.h"
#include "Timer/timer.01.h"

const double pi = 3.141592653589793;

#define SQR(x) ((x)*(x))


const char vertexShader[] = 
{    
	
	"uniform float pointSizeFactor;     // point texture size factor        \n"
	"void main()                                                            \n"
	"{                                                                      \n"
	"    float pointSize = 1.0*pointSizeFactor;										\n"
	"    if (pointSizeFactor<1) pointSize = 1.0*gl_Point.size;					\n"
//	"    float pointSize = 1.0*gl_Point.size;											\n"
	"    vec4 vert = gl_Vertex;															\n"
	"    vert.w = 1.0;																		\n"
	"    vec3 pos_eye = vec3 (gl_ModelViewMatrix * vert);                   \n"
//	"    gl_PointSize = 1.0*pointSizeFactor / (1.0 - 0.1*pos_eye.z);  \n"
	"    gl_PointSize = pointSize;								\n"	
	"    gl_TexCoord[0] = gl_MultiTexCoord0;                                \n"
	"    gl_Position = ftransform();                                        \n"
//	"    gl_FrontColor = gl_Color+pointSizeFactor*0;                        \n"
//	"	  gl_FrontColor=vec4(gl_Color.r+float(pointSizeFactor)*0.0, gl_Color.g, gl_Color.b, gl_Color.a);\n"
	"    gl_FrontSecondaryColor = gl_SecondaryColor;                        \n"
	"}                                                                      \n"
};

const char pixelShader[] =
{
	"uniform sampler2D splatTexture;                                        \n"

	"void main()                                                            \n"
	"{                                                                      \n"
	"    vec4 color2 = gl_SecondaryColor;                                   \n"
	"    vec4 color = 																		\n"
	"			(0.6+0.4*gl_Color)*texture2D(splatTexture, gl_TexCoord[0].st); \n"
	"    gl_FragColor = color* color2;													\n"
	"}                                                                      \n"
};





class Visualizator: public QGLWidget {

	public:
		
		class Eye {
			public:	
		
				typedef float T;
		
				Eye(T Rx, T Ry, T Rz, T Cx, T Cy, T Cz, T Ux, T Uy, T Uz, T _fov):
				R(Rx,Ry,Rz), RM(R),
				U(Ux,Uy,Uz), UM(U),
				C(Cx,Cy,Cz), CM(C),
				PX(1,0,0), PY(0,1,0), PZ(0,0,1),		
				fov(_fov)
				{}
			
				fVector R,RM, U,UM,  C,CM; // M - memory
				fVector PX, PY, PZ;
		
				T fov;
		
				void rotateXY(T x, T y, bool apply){
	
					R=RM;	U=UM;
	
					T a1 = 0.5*pi-atan2(y,x); // angle btw U and XY
					T a2 = pi*sqrt(x*x+y*y);  // mouse shift on screen height (on 1) leads to rotion on pi 

					fVector RC = R-C;
					fVector XY = ROTATE(U, U%RC, a1);

					R = ROTATE(RC, XY, -a2)+C; // camera and object view motion are oposit 
					fVector XY_new = ROTATE(XY, RC, a2); // folow by the RV vector
	
					U = ORT(ROTATE(XY_new, ORT(XY%RC), -a1));
	
					if (apply) { RM = R; UM = U;}
	
				}	
		
				void rotateZ(T a, bool apply) {
	
					U = ROTATE(UM, UM%(R-C), a);

					if (apply) UM=U;
		
				}
		
				void moveZ(T z){

					RM=R = z*ORT(R-C)+C;

				}
		
			private:
		
				fVector ROTATE(fVector V1, fVector V2, T a){
					return module(V1)*((T)cos(a)*ORT(V1)+ (T)sin(a)*ORT(V2));	
				}

				fVector ORT(fVector V){ 
					return V.ort(); 
				}

				Eye::T module(fVector V){ 
					return V.module(); 
				}
		
		} eye;
		
		float scale;
		float scale_steps;
		float timeStep;
		float timeFlag;
		
		bool perspectiveF;
		bool gridF;
		bool pivotF;
		bool infoF;
		
		Timer wallTime;
		int glTimerID, fpsTimerID;
		
		int frames;
		double framesTime;
		double fps;
		
		Visualizator(QWidget *parent=0): QGLWidget(parent),
		eye(0,0,3, 0,0,0, 0,1,0, 45),
		mouseEvent0(0), 
		scale(0), scale_steps(10),
		perspectiveF(true),
		gridF(true),
		pivotF(true),
		infoF(true),
		timeStep(17), // [ms]
		frames(0),
		framesTime(0),
		fps(0)
		{ 
			setAutoFillBackground(false);
			wallTime.set();
			wallTime.sin = 0.001*timeStep;
			glTimerID = startTimer(timeStep);
			fpsTimerID = startTimer(250); // fps info time
			timeFlag = true;
		};
		
		
		void timerEvent(QTimerEvent *event) {
			
			if (event->timerId() == glTimerID) {
				
				wallTime.set();
				if (timeFlag) moveTime(wallTime.sin+wallTime.sout);
				updateGL();
				
				framesTime+=wallTime.sin+wallTime.sout;
				frames++;
				wallTime.get();
				
			}
			
			if (event->timerId() == fpsTimerID){
				fps = frames/framesTime;
				framesTime=frames=0;
			}
			
			
		}
		
		virtual void moveTime(double dt){
			/*
			for(int i=0; i<N; i++) {
				ROTATE(o[i].r, w*dt);
				o[i].v = w%o[i].r;
			}
			*/
		}
		
		void ROTATE(dVector &R, dVector w){
			
			if ((R%w).module() == 0) return;
			
			dVector wz = w.ort();
			dVector wy = (w%R).ort();
			dVector wx = (wz%wy).ort();
							
			dVector rz = (R*wz)*wz;
			dVector rx = (R*wx)*wx;
							
			rx = rx.rotate(wy, w.module());
				
			R = rz+rx;

		}
		
		
		void initializeGL() {  // Set up the rendering context, define display lists etc.:

			glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
			glShadeModel(GL_FLAT);
			glClearDepth(1.0);							// Enables Clearing Of The Depth Buffer
			glDepthFunc(GL_LESS);
			glEnable(GL_DEPTH_TEST);					// Enables Depth Testing
			glEnable(GL_CULL_FACE);

		}

		void resizeGL(int w, int h){  // setup viewport, projection etc.:
			glViewport(0, 0, w, h);
		}
		
		virtual void paintGL() {
			
			glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
			

			glMatrixMode ( GL_PROJECTION );
			glLoadIdentity(); // загружает еденичную матрицу
			
			float aspect = (float)width()/height();
			
			perspectiveF ? gluPerspective(75, aspect, 0.001, 100) 
							 : glOrtho(-aspect, +aspect, -1, 1, -1000, 1000);
			
			
			glMatrixMode(GL_MODELVIEW); 
			glLoadIdentity();
			gluLookAt(	eye.R.x, eye.R.y, eye.R.z, 
							eye.C.x, eye.C.y, eye.C.z, 
							eye.U.x, eye.U.y, eye.U.z );
			
			float _scale = pow(10, scale);
			glScalef(_scale, _scale, _scale);

			
			
			
			glColor3f (1,.5,0);
			glPointSize(1);
			glBegin(GL_LINES);
			glVertex3f(0,0,0);
			glVertex3f(1,1,0);
			glEnd();

			
			
			if (pivotF) drawPivot();
			if (gridF) drawGrid();
			if (infoF) printInfo();
			
			/*
			// draw points
			glBlendFunc(GL_SRC_ALPHA, GL_ONE);
			glEnable(GL_BLEND);			
			glDepthMask(GL_FALSE);
			glEnable(GL_POINT_SPRITE_ARB);
			glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
			glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_NV);
			glPointSize(spriteSize);
			glBlendFunc(GL_SRC_ALPHA, GL_ONE);
			glEnable(GL_BLEND);
			glDepthMask(GL_FALSE);

			
			
			
			
			glDisable(GL_POINT_SPRITE_ARB);
			glDisable(GL_BLEND);
			glDepthMask(GL_TRUE);
			*/
			
			
		}
		
		void printInfo(){
			
			
			char str[512];
			sprintf(str, "fps: %.1f\nscale: %.4g\n", fps, pow(10, scale) );
			glColor3f (.4,.4,.4);
			glPrint(10,20, str, GLUT_BITMAP_HELVETICA_12);
			
		}
		
		
		void drawPivot(){
			
			// PIVOT
			glPointSize(1);
			glBegin(GL_LINES);
			glColor3f (.5,0,0);
			glVertex3f(0,0,0);
			glVertex3f(1,0,0);
			
			glColor3f (0,.5,0);
			glVertex3f(0,0,0);
			glVertex3f(0,1,0);
   
			glColor3f (0,0,.5);
			glVertex3f(0,0,0);
			glVertex3f(0,0,1);
			glEnd();
			
			// draw axis lables
			glColor3f(0.7,0.4,0.4);
			glPrint(fVector(1.03,0,0), "x", GLUT_BITMAP_HELVETICA_12);
			glColor3f(0.4,0.7,0.4);
			glPrint(fVector(0,1.03,0), "y", GLUT_BITMAP_HELVETICA_12);
			glColor3f(0.4,0.4,.7);
			glPrint(fVector(0,0,1.03), "z", GLUT_BITMAP_HELVETICA_12);
			
		}
		
		void drawGrid(){
			
			glColor3f(0.1,0.1,0.1);
			glBegin(GL_LINES);
			for (float x=-1; x<1; x+=0.1) {
				glVertex3f(x,-1,0);
				glVertex3f(x,+1,0);
				glVertex3f(-1,x,0);
				glVertex3f(+1,x,0);
				glVertex3f(0,-1,x);
				glVertex3f(0,+1,x);
				glVertex3f(-1,0,x);
				glVertex3f(+1,0,x);
				glVertex3f(x,0,-1);
				glVertex3f(x,0,+1);
				glVertex3f(0,x,-1);
				glVertex3f(0,x,+1);
			}
			glEnd();
			
		}
		
		
		QMouseEvent *mouseEvent0; 
		
		void onMouseMoveRelease(QMouseEvent event0, QMouseEvent event1, bool apply){
				
			const float pi = 3.141592654;
	
			// screen center coordinates on press
			float x0 =  (event0.x()-0.5*width())  / height();
			float y0 = -(event0.y()-0.5*height()) / height();
	
			// screen center coordinates on move / release
			float x1 =  (event1.x()-0.5*width())  / height();
			float y1 = -(event1.y()-0.5*height()) / height();
	
			float dx = x1-x0;
			float dy = y1-y0;
	
			float a0 = atan2(y0,x0);
			float a1 = atan2(y1,x1);
	
			float da = a1-a0; 
	
			//int modifers = QApplication::keyboardModifiers();
			int buttons = event0.buttons();
			
			// Rotation XY
			if (buttons == Qt::LeftButton)
				eye.rotateXY(dx, dy, apply);
	
			// Rotation around Z
			if (buttons == Qt::RightButton)
				eye.rotateZ(da, apply);
			
		}
		
		void wheelEvent(QWheelEvent *event){
			scale+=-(event->delta()/120.0f)/scale_steps;
		}
		
		void mousePressEvent(QMouseEvent *event){
			mouseEvent0 = new QMouseEvent(*event);
		}

		void mouseMoveEvent(QMouseEvent *event) {
	      onMouseMoveRelease(*mouseEvent0, *event, false);
		}

		void mouseReleaseEvent(QMouseEvent *event){
			
			if (event->buttons()!=0) return; //??
			onMouseMoveRelease(*mouseEvent0, *event, true);

			if (mouseEvent0) delete mouseEvent0; 
			
		}
		
		double w_a1, w_a2;
		
		virtual void keyPressEvent(QKeyEvent *event){
			
			if (event->key() == Qt::Key_F) 
				setWindowState(windowState()^Qt::WindowFullScreen);
			
			if (event->key() == Qt::Key_Space)
				timeFlag = !timeFlag;
			
			if (event->key() == Qt::Key_X) {
				eye.RM = eye.R = eye.R.module()*fVector(1,0,0);
				eye.UM = eye.U = fVector(0,0,1);
			}
			
			if (event->key() == Qt::Key_Y) {
				eye.RM = eye.R = -eye.R.module()*fVector(0,1,0);
				eye.UM = eye.U = fVector(0,0,1);
			}

			if (event->key() == Qt::Key_P) perspectiveF=!perspectiveF;
			if (event->key() == Qt::Key_G) gridF=!gridF;
			if (event->key() == Qt::Key_V) pivotF=!pivotF;
			if (event->key() == Qt::Key_QuoteLeft) infoF=!infoF; // ~
				

			if (event->key() == Qt::Key_Z) {
				eye.RM = eye.R = eye.R.module()*fVector(0,0,1);
				eye.UM = eye.U = fVector(0,1,0);
			}				
			
			
			
			if (event->key() == Qt::Key_Escape)
				if (windowState()) setWindowState(windowState()^Qt::WindowFullScreen);
				else exit(0);
			
		}
		
		
		dVector V;
		void glPrint(fVector R, const char *string, void *font) {
			glRasterPos3fv(R);
			int len = (int) strlen(string);
			for (int i = 0; i < len; i++) 
				glutBitmapCharacter(font, string[i]);
		}	
		
		
		
		void glPrint(int x, int y, const char *string, void *font) {
	
			// save current projection matrix
			glMatrixMode(GL_PROJECTION); 
			glPushMatrix();
	
			// get viewport size
			GLint viewport[4];
			glGetIntegerv(GL_VIEWPORT, viewport );
	
			// set projection matrix to ortho settings
			glLoadIdentity();
			glOrtho(0, viewport[2], viewport[3], 0, -1, 1);
	
			// save current modelview matrix
			glMatrixMode(GL_MODELVIEW); 
			glPushMatrix();
	
			//text drowing
			glLoadIdentity();

			int indent = 15; //
			glRasterPos2i(x, y);
			int len = (int) strlen(string);
			for (int i = 0; i < len; i++) {
		
				if (string[i] == '\n') {
					y+=indent;
					glRasterPos2i(x, y);
					continue;
				}
				glutBitmapCharacter(font, string[i]);
		
			}
	
			// restore previos settings
			glMatrixMode(GL_PROJECTION); 
			glPopMatrix();
	
			glMatrixMode(GL_MODELVIEW); 
			glPopMatrix();
			
		}
		
	/*
	void createTexture(int resolution)	{
		
		unsigned char* data = createGaussianMap(resolution);
		
		glGenTextures(1, (GLuint*)&m_texture);
		glBindTexture(GL_TEXTURE_2D, m_texture);
		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, resolution, resolution, 0, 
                 GL_RGBA, GL_UNSIGNED_BYTE, data);
    
	}	
	*/	
		
};




//------------------------------------------------------------------------------
// Function     	  : EvalHermite
//------------------------------------------------------------------------------
/**
* EvalHermite(float pA, float pB, float vA, float vB, float u)
* @brief Evaluates Hermite basis functions for the specified coefficients.
*/ 
inline float evalHermite(float pA, float pB, float vA, float vB, float u)
{
    float u2=(u*u), u3=u2*u;
    float B0 = 2*u3 - 3*u2 + 1;
    float B1 = -2*u3 + 3*u2;
    float B2 = u3 - 2*u2 + u;
    float B3 = u3 - u;
    return( B0*pA + B1*pB + B2*vA + B3*vB );
}


unsigned char* createGaussianMap(int N)
{
    float *M = new float[2*N*N];
    unsigned char *B = new unsigned char[4*N*N];
    float X,Y,Y2,Dist;
    float Incr = 2.0f/N;
    int i=0;  
    int j = 0;
    Y = -1.0f;
    //float mmax = 0;
    for (int y=0; y<N; y++, Y+=Incr)
    {
        Y2=Y*Y;
        X = -1.0f;
        for (int x=0; x<N; x++, X+=Incr, i+=2, j+=4)
        {
            Dist = (float)sqrtf(X*X+Y2);
            if (Dist>1) Dist=1;
            M[i+1] = M[i] = evalHermite(1.0f,0,0,0,Dist);
            B[j+3] = B[j+2] = B[j+1] = B[j] = (unsigned char)(M[i] * 255);
        }
    }
    delete [] M;
    return(B);
}




















